`timescale 1 ns/1 ns

// 模块：top
// 功能：FOC 使用示例，是FPGA工程的顶层模块，控制电机的切向力矩一会顺时针一会逆时针，同时可以通过 UART 监测电流环控制的跟随曲线
// 参数：无
// 输入输出：详见下方注释
module top(
    input  wire clk_50m, // 50MHz 时钟
    // ------- 3相 PWM 信号，（包含使能信号） -----------------------------------------------------------------------------------------------------
    output wire pwm_en,  // 3相共用的使能信号，当 pwm_en=0 时，6个MOS管全部关断。
    output wire pwm_a,   // A相PWM信号。当 =0 时。下桥臂导通；当 =1 时，上桥臂导通
    output wire pwm_b,   // B相PWM信号。当 =0 时。下桥臂导通；当 =1 时，上桥臂导通
    output wire pwm_c,   // C相PWM信号。当 =0 时。下桥臂导通；当 =1 时，上桥臂导通
    // ------- AD7928 (ADC 芯片) ，用于相电流检测 (SPI接口) ---------------------------------------------------------------------------------------
    output wire spi_ss,
    output wire spi_sck,
    output wire spi_mosi,
    input  wire spi_miso,
    // ------- AS5600 磁编码器，用于获取转子机械角度 (I2C接口) ------------------------------------------------------------------------------------
    output wire i2c_scl,
    inout       i2c_sda,
    // ------- UART: 打印转子直角坐标系下的 d 轴实际电流(id)、d 轴目标电流(id_aim)、q 轴实际电流(iq)、q 轴目标电流(iq_aim) ------------------------
    output wire uart_tx
);

wire               rstn;        // 复位信号，初始为0，当PLL锁相成功后置1。
wire               clk;         // 时钟信号，频率可取几十MHz。控制频率 = 时钟频率 / 2048。比如本例取时钟频率为 36.864MHz ，那么控制频率为 36.864MHz/2048=18kHz。（控制频率 = 3相电流采样的采样率 = PID算法的控制频率 = SVPWM占空比的更新频率）

wire        [11:0] phi;         // 从 AS5600 磁编码器读出的转子机械角度 φ ，取值范围0~4095。0对应0°；1024对应90°；2048对应180°；3072对应270°。

wire               sn_adc;      // 3相电流 ADC 采样时刻控制信号，当需要进行一次采样时，sn_adc 信号上产生一个时钟周期的高电平脉冲，指示ADC应该进行采样了。
wire               en_adc;      // 3相电流 ADC 采样结果有效信号，sn_adc 产生高电平脉冲后，adc_ad7928 模块开始采样3相电流，在转换结束后，在 en_adc 信号上产生一个周期的高电平脉冲，同时把 ADC 转换结果产生在 adc_value_a, adc_value_b, adc_value_c 信号上。
wire        [11:0] adc_value_a; // A 相电流检测 ADC 原始值
wire        [11:0] adc_value_b; // B 相电流检测 ADC 原始值
wire        [11:0] adc_value_c; // C 相电流检测 ADC 原始值

wire               en_idq;      // 出现高电平脉冲时说明 id 和 iq 出现了新值，每个控制周期 en_idq 会产生一个高电平脉冲
wire signed [15:0] id;          // 转子 d 轴（直轴）的实际电流值，
wire signed [15:0] iq;          // 转子 q 轴（交轴）的实际电流值，可正可负（若正代表逆时针，则负代表顺时针，反之亦然）
wire signed [15:0] id_aim;      // 转子 d 轴（直轴）的目标电流值，可正可负
reg  signed [15:0] iq_aim;      // 转子 q 轴（交轴）的目标电流值，可正可负（若正代表逆时针，则负代表顺时针，反之亦然）



// PLL，用 50MHz 时钟产生 36.864 MHz 时钟
// 注：该模块仅适用于 Altera Cyclone IV FPGA ，对于其他厂家或系列的FPGA，请使用各自相同效果的IP核/原语（例如Xilinx的clock wizard）代替该模块。
pll pll_i (
	.inclk0       ( clk_50m        ), // input : clk_50m
	.c0           ( clk            ), // output: clk
	.locked       ( rstn           )  // output: rstn
);



// AS5600 磁编码器读取器，内含简易 I2C 控制器，通过 I2C 接口读取当前转子机械角度 φ
as5600_read #(
    .CLK_DIV      ( 16'd10         )  // i2c_scl 时钟信号分频系数，scl频率 = clk频率 / (4*CLK_DIV) ，例如在本例中 clk 为 36.864MHz，CLK_DIV=10，则 SCL 频率为 36864/(4*10) = 922kHz 。注，AS5600 芯片要求 SCL 频率不超过 1MHz
) as5600_i (
    .rstn         ( rstn           ),
    .clk          ( clk            ),
    .scl          ( i2c_scl        ), // I2C 接口： SCL
    .sda          ( i2c_sda        ), // I2C 接口： SDA
    .o_en         (                ), // output: 每成功读取一次 φ，o_en就产生一个高电平脉冲，这里我们用不到该信号
    .o_phi        ( phi            )  // output: 转子机械角度 φ
);



// AD7928 ADC 读取器，用于读取3相电流采样值（读出的是未经任何处理的ADC原始值）
adc_ad7928 #(
    .CH_CNT       ( 3'd2           ), // 该参数取2，指示我们只想要 CH0, CH1, CH2 这三个通道的 ADC 值
    .CH0          ( 3'd1           ), // 指示 CH0 对应 AD7928 的 通道1。（硬件上 A 相电流连接到 AD7928 的 通道1）
    .CH1          ( 3'd2           ), // 指示 CH1 对应 AD7928 的 通道2。（硬件上 B 相电流连接到 AD7928 的 通道2）
    .CH2          ( 3'd3           )  // 指示 CH2 对应 AD7928 的 通道3。（硬件上 C 相电流连接到 AD7928 的 通道3）
) adc_ad7928_i (
    .rstn         ( rstn           ),
    .clk          ( clk            ),
    .spi_ss       ( spi_ss         ), // SPI 接口：SS
    .spi_sck      ( spi_sck        ), // SPI 接口：SCK
    .spi_mosi     ( spi_mosi       ), // SPI 接口：MOSI
    .spi_miso     ( spi_miso       ), // SPI 接口：MISO
    .i_sn_adc     ( sn_adc         ), // input : 当 sn_adc 出现高电平脉冲时，该模块开始进行一次（3路的）ADC 转换
    .o_en_adc     ( en_adc         ), // output: 当转换结束后，en_adc 产生一个周期的高电平脉冲
    .o_adc_value0 ( adc_value_a    ), // 当 en_adc 产生一个周期的高电平脉冲的同时，adc_value_a 上出现 A 相电流的 ADC 原始值
    .o_adc_value1 ( adc_value_b    ), // 当 en_adc 产生一个周期的高电平脉冲的同时，adc_value_b 上出现 B 相电流的 ADC 原始值
    .o_adc_value2 ( adc_value_c    ), // 当 en_adc 产生一个周期的高电平脉冲的同时，adc_value_c 上出现 C 相电流的 ADC 原始值
    .o_adc_value3 (                ), // 忽略其余 5 路 ADC 转换结果
    .o_adc_value4 (                ), // 忽略其余 5 路 ADC 转换结果
    .o_adc_value5 (                ), // 忽略其余 5 路 ADC 转换结果
    .o_adc_value6 (                ), // 忽略其余 5 路 ADC 转换结果
    .o_adc_value7 (                )  // 忽略其余 5 路 ADC 转换结果
);



// FOC + SVPWM 模块 （使用方法和原理详见 foc_top.sv）
foc_top #(
    .INIT_CYCLES  ( 16777216       ), // 本例中，时钟(clk)频率为 36.864MHz，INIT_CYCLES=16777216，则初始化时间为 16777216/36864000=0.45 秒
    .ANGLE_INV    ( 1'b0           ), // 本例中，角度传感器没装反（A->B->C->A 的旋转方向与 φ 增大的方向相同），则该参数应设为 0
    .POLE_PAIR    ( 8'd7           ), // 本例使用的电机的极对数为 7
    .MAX_AMP      ( 9'd384         ), // 384 / 512 = 0.75。说明 SVPWM 的最大振幅 占 最大振幅极限的 75%
    .SAMPLE_DELAY ( 9'd120         ), // 采样延时，取值范围0~511，考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间，所以从3个下桥臂都导通，到 ADC 采样时刻之间需要一定的延时。该参数决定了该延时是多少个时钟周期，当延时结束时，该模块在 sn_adc 信号上产生一个高电平脉冲，指示外部 ADC “可以采样了”
    .Kp           ( 24'd32768      ), // 电流环 PID 控制算法的 P 参数
    .Ki           ( 24'd2          )  // 电流环 PID 控制算法的 I 参数
) foc_top_i (
    .rstn         ( rstn           ),
    .clk          ( clk            ),
    .phi          ( phi            ), // input : 角度传感器输入（机械角度，简记为φ），取值范围0~4095。0对应0°；1024对应90°；2048对应180°；3072对应270°。
    .sn_adc       ( sn_adc         ), // output: 3相电流 ADC 采样时刻控制信号，当需要进行一次采样时，sn_adc 信号上产生一个时钟周期的高电平脉冲，指示ADC应该进行采样了。
    .en_adc       ( en_adc         ), // input : 3相电流 ADC 采样结果有效信号，sn_adc 产生高电平脉冲后，外部ADC开始采样3相电流，在转换结束后，应在 en_adc 信号上产生一个周期的高电平脉冲，同时把ADC转换结果产生在 adc_a, adc_b, adc_c 信号上
    .adc_a        ( adc_value_a    ), // input : A 相 ADC 采样结果
    .adc_b        ( adc_value_b    ), // input : B 相 ADC 采样结果
    .adc_c        ( adc_value_c    ), // input : C 相 ADC 采样结果
    .pwm_en       ( pwm_en         ),
    .pwm_a        ( pwm_a          ),
    .pwm_b        ( pwm_b          ),
    .pwm_c        ( pwm_c          ),
    .en_idq       ( en_idq         ), // output: 出现高电平脉冲时说明 id 和 iq 出现了新值，每个控制周期 en_idq 会产生一个高电平脉冲
    .id           ( id             ), // output: d 轴（直轴）的实际电流值，可正可负
    .iq           ( iq             ), // output: q 轴（交轴）的实际电流值，可正可负（若正代表逆时针，则负代表顺时针，反之亦然）
    .id_aim       ( id_aim         ), // input : d 轴（直轴）的目标电流值，可正可负，在不使用弱磁控制的情况下一般设为0
    .iq_aim       ( iq_aim         ), // input : q 轴（直轴）的目标电流值，可正可负（若正代表逆时针，则负代表顺时针，反之亦然）
    .init_done    (                )  // output: 初始化结束信号。在初始化结束前=0，在初始化结束后（进入FOC控制状态）=1
);



reg [23:0] cnt;
always @ (posedge clk or negedge rstn)   // 该 always 维护一个 24bit 的 自增计数器
    if(~rstn)
        cnt <= 24'd0;
    else
        cnt <= cnt + 24'd1;


assign id_aim = $signed(16'd0);          // 令 id_aim 恒等于 0

always @ (posedge clk or negedge rstn)   // 该 always 块令 iq_aim 交替地取 +200 和 -200 ，即电机的切向力矩一会顺时针一会逆时针
    if(~rstn) begin
        iq_aim <= $signed(16'd0);
    end else begin
        if(cnt[23])
            iq_aim <=  $signed(16'd200); // 令 id_aim = +200
        else
            iq_aim <= -$signed(16'd200); // 令 id_aim = -200
    end



// UART 发送器(监视器)，格式为：115200,8,n,1
uart_monitor #(
    .CLK_DIV      ( 16'd320        )   // UART分频倍率，在本例中取320。因为时钟频率为 36.864MHz, 36.864MHz/320=115200
) uart_monitor_i (
    .rstn         ( rstn           ),
    .clk          ( clk            ),
    .i_en         ( en_idq         ),  // input: 当 en_idq 上出现高电平脉冲时，启动 UART 发送
    .i_val0       ( id             ),  // input: 以十进制的形式发送变量 id
    .i_val1       ( id_aim         ),  // input: 以十进制的形式发送变量 id_aim
    .i_val2       ( iq             ),  // input: 以十进制的形式发送变量 iq
    .i_val3       ( iq_aim         ),  // input: 以十进制的形式发送变量 iq_aim
    .o_uart_tx    ( uart_tx        )   // output: UART 发送信号
);

endmodule
